最後幾天想來談談一些在平常開發時息息相關的東西。
這段時間提到了很多跟測試相關的東西,讓我們回歸到一個點上,怎麼讓程式更有可測試性?
不過在這之前還是要先來看看為什麼需要讓程式有可測試性。
平常開發人員在設計程式時只需要關注軟體需要有哪些功能,最終的 User 想要的又是什麼。但是還有一些使用者他們想要的是更好、更穩定的產品。
關於這點最基本的要求就是讓程式有可測試性,因為我們可以透過測試來保證我們的程式具有一定的品質。良好的測試通常具有以下特點:
現在圈子裡流行的 TDD (測試驅動開發) 就是一個很好的例子,由於先寫測試的緣故,程式天然擁有很好的可測試性,且通常寫出來的測試也具有以上的特性。
了解上述的內容後,再來看看設計出一個可測試的程式需要注意哪些內容。
在這方面推薦遵循 SOLID 裡的 OCP 原則 來實作。 OCP 常常跟 interface 一起提到,而我自己在實作時也常使用 interface 來實現,這樣在測試時可以利用 interface mock 一個假的物件,而不影響實際的內容。
事實上遵循一些設計原則開發程式,往往也會讓程式有比較好的可測試性。
以下列出幾個自己平常會注意的設計特點,當然還有其他要注意的,也希望有其他人再幫我補充:
1. 開發時的方法盡量使用抽象方法
測試時可以透過繼承來 override 原本方法的行為。
2. 使用 interface
第一點的強化版,可以透過 多型 做出假的物件,甚至可以消除對外部的依賴。
3. 避免在有邏輯的方法內初始化物件
這樣可以把偽造的物件傳給方法,而不必強迫使用真實的物件。
4. 避免直接呼叫靜態方法 & 類
盡量透過間接的方法呼叫,避免依賴靜態方法,因為靜態方法很難被 override。
5. 不要在建構式裡寫邏輯
同上,建構式也很難被 override,同時讓建構式的內容比較簡單也可以讓類更容易被繼承。
但是一昧追求程式的可測試性也有自己的問題,事實上他也有他自己的缺點及爭議。
前面講了這麼多,但其實不需要刻意追求讓程式更有可測試性,大部分的情況下,遵循一些設計原則就能讓程式有一定的測試性。在設計程式時要考慮的因素很多,追求可測試性可能會有以下的問題
大多數情況情況下我們會為了讓程式碼更好被測試而額外寫了更多程式,而這就是多出來的工作量,從而衍生許多問題。
如果功能本身很簡單,我們還需要再多寫這麼多程式來保證可測試性嗎?
又或者現在的設計本身已經能夠應付業務需求了,這時想要為了讓程式碼能夠更好的被測試而多寫了更多東西很可能會增加不可預知的風險,要知道現在多寫的程式並不是產品本身的功能。
只能說讓程式可以更好的被測試是有它的價值的,但以上也是需要討論的議題,開發程式時都是要被考慮的。
當程式變得更加抽象後,程式碼的複雜度就會上升,有時會讓事情變得更加複雜了。同時大量使用 interface 抽象化程式後,也會讓程式碼在閱讀上比較困難一些。
為了讓程式碼更好被測試,有時候會公開一些方法或是 API , 但是公開這些在專案內的東西可能會有安全或是法律上的問題,考慮到這些因素,有時候我們不得不在某些地方作出讓步。
並不是在寫任何軟體都需要考慮所謂的可測試性,尤其是一些動態語言,因為他們的特性讓程式碼天生就是可測試的,動態語言可以自由的在 compile 階段抽換掉任何物件。
既然如此,當然就不需要為了測試使用 interface ,開放某些 API 等,甚至不一定要使用前面提到的一些設計原則。
使用動態語言可以發揮各式各樣的想像力,甚至可以用比較骯臟的方式呼叫底層的 API 。值得一提的是這樣的程式碼設計得很糟糕,但偏偏他們又都具備了良好的可測試性。
所以這時候更需要考慮的是程式碼的設計問題,讓程式更為簡潔、好維護及好理解等,不過這就不是這次所要討論的重點了。
在大多數情況下,只有靜態語言才會強調 可測試性 這個問題,因為要達到這個目標必須要透過各種設計方式完成,而 SOLID 原則更是與可測試性有很大的關聯,既可保證程式的可測試性,也讓程式碼有良好的設計。
動態語言就不需要注意這個問題了,由於語言特性的原因,所以有時候缺乏可測試性對開發人員來說並不代表這是不好的設計,因為這個問題可以從其他方面來解決。他們可以更專注在如何更好的設計程式來達到開發目的。
最後做個總結:
擁有高度可測試性的程式不見得是一個良好的程式設計,他充其量只是在開發設計時的一個比較好的副產物。不用太過於追求這一點,而是應該要持續追求更良好的程式設計。
今天就到這邊,內文還有許多不足的地方甚至是有爭議的東西,也歡迎大家討論。